#include "dct_def2.hpp" // New Version
#include "dct_impl2.hpp"

#include "kmeans_def.hpp"
#include "kmeans_impl.hpp"
// 
#include "emopt_def.hpp"
#include "emopt_impl.hpp"



inline
BoT::BoT(const uword in_Ng,double in_ima_rows, double in_ima_cols)
:Ng(in_Ng)
{
  ima_rows = in_ima_rows;
  ima_cols = in_ima_cols;
  
}



inline
mat
BoT::convert2Arma(cv::Mat cvImage)
{
  //resize(cvImage, cvImage, cv::Size( cvImage.cols/rescal,cvImage.rows/rescal) );
  //cout << "row: " << cvImage.rows << endl;
  running_stat<double> stats;
  
  mat armaImage(cvImage.rows, cvImage.cols);
  
  for (uword row = 0; row< cvImage.rows; row++)
  {
    for (uword col = 0; col< cvImage.cols; col++)
    {
      armaImage(row,col) =  cvImage.at<uchar> (row, col);
      stats(armaImage(row,col));
    }
  }
  //cout<< "Normalization:" << endl;
  //armaImage = (armaImage - stats.mean())/stats.stddev();
  return armaImage;
}


inline
void
BoT::create_dictionary(const std::string list_frames_train)
{
  //cout << "Starting create_dictionary(): " << endl;
  
  field<std::string> 	names;
  field<mat>		gmm_generic; 
  uword 		N = 8; //for dct
  //cout << list_frames_train << endl;
  names.load(list_frames_train);
  uword n_frames = names.n_rows;
  
  double jump = 2; // jumping two pixels
  
  double w    = ima_cols;  
  double h    = ima_rows; 
  
  // To overlap blocks:
  double lw = floor((w - N)/jump + 1);
  double lh = floor((h - N)/jump + 1);
  double l  = lw*lh; 
  //cout << "l= " << l << endl;
  mat coef_one_frame; //Dimension = 15, #feature/frame * # frames
  coef_matrix.zeros(15,l*n_frames);
  cout << "Initializing DCT " << endl;
  dct2d dct(N);
  
  //cout << "Starting Bag of Features " << endl;
  
  for (uword i = 0; i < n_frames; ++i)
  {
    mat frame;
    std::stringstream name;
    name << "./dictionary/" << names(i,0); // In folder Dictionary is saved all the training frames
    //cout << " Frame: " << name.str() << endl;
    cv::Mat cvImage;
    cvImage = cv::imread(name.str(), 0); //0= CV_LOAD_IMAGE_GRAYSCALE??
    frame = convert2Arma(cvImage); // Converting from OpenCV to Armadillo"<< endl;
    coef_one_frame = dct.get_coeff_overlap_6(frame); // Calculate DCT features, overlapping 6 pixels
    uword ci = l*i;
    uword cf = (l*i+l) - 1;
    coef_matrix.submat( span::all, span( ci,cf ) ) = coef_one_frame;
  }
  
  
  //cout << " Generating Model ... ";
  //cout << endl << endl << endl << endl;
  
  gmm_generic = gmm_model(); // Copy from frames_recognition
  
  std::stringstream  model;
  model << "dict_"<< Ng;
  //cout << endl << endl << endl << endl;
  
  gmm_generic.save(model.str());
  model_name = model.str();
  //   
  
  means   = gmm_generic(0);
  covs    = gmm_generic(1);
  weights = gmm_generic(2);
  weights.reshape( means.n_cols,1 );
  
}


inline
vec
BoT::calculate_features(const cv::Mat &in_frame)
{
  //cout << "Loading Generic Model " << endl;
  field<mat>		gmm_generic; 
  std::stringstream  model;
  model << "dict_"<< Ng;
  model_name = model.str();
  
  gmm_generic.load(model_name); // 
  means   = gmm_generic(0);  // Only using the means
  
  //cout << "Calculating  BoF" << endl;
  
  field<std::string> 	names;
  uword 		N = 8; //for dct 8*8 blocks
  
  
  //names = list_frames_test;
  //uword n_frames = names.n_rows;
  
  double jump = 2; // jumping two pixels
  
  double w    = ima_cols; // Think a better solution 
  double h    = ima_rows; 
  //l = # of features set per image
  double lw = (w - N)/jump + 1;
  double lh = (h - N)/jump + 1;
  double l  = lw*lh; 
  
  mat coef_one_frame; //Dimension = 15, #feature/frame * # frames
  //cout << "Initializing DCT " << endl;
  dct2d dct(N);
  
  //cout << "Starting Bag of Features " << endl;
  
  mat frame = convert2Arma(in_frame);
  coef_one_frame = dct.get_coeff_overlap_6(frame);
  //cout << "size coef_one_frame= ( " << coef_one_frame.n_rows << "," << coef_one_frame.n_cols << " )" << endl;
  
  //vec distances;
  vec features_BoW;
  
  features_BoW.zeros(Ng);
  uword best_mi;
  double act_dist;
  double min_dist;
  
  for (uword ni = 0; ni < coef_one_frame.n_cols; ++ni) // # of vectors    
    {
      min_dist = norm( (coef_one_frame.col(ni) - means.col(0)), 2 );
      best_mi = 0;
      for (uword mi = 1; mi < Ng; ++mi) // # of means
      {
	act_dist = norm( (coef_one_frame.col(ni) - means.col(mi)), 2 );
	
	if (act_dist < min_dist)
	{
	  best_mi = mi;
	  min_dist = act_dist;
	}
      }
      features_BoW (best_mi) += 1;//One_features_BoW;
    }
    
    features_BoW = features_BoW /  coef_one_frame.n_cols;
    return features_BoW;
}


inline 
field<mat> 	
BoT::gmm_model()
{
  
  //field <mat>	gmm_one_model(3); // #frames,(3=means +covs + weights)
  uword max_iter = 1; // EM is iterated only 1
  //cout << " Doing k-means ..." ;
  kmeans km( coef_matrix, Ng );// data, Ncent
  //cout << endl << endl << endl << endl;
  //cout << "coef_matrix   " << coef_matrix.n_elem << endl;
  km.run( 10 ); // 
  
  //cout << "coef_matrix   " << coef_matrix.n_elem << endl;
  //cout << "km.get_means()   " << km.get_means()   << endl;
  //cout << "km.get_covs()    " << km.get_covs()    << endl;
  //cout << "km.get_weights() " << km.get_weights() << endl;
  emopt em( coef_matrix, km.get_means(), km.get_covs2(), km.get_weights() ); // updated to get_covs2
  //cout << " Doing EM ..." ;
  //cout << endl << endl << endl << endl;
  em.run(max_iter); //max_iter
  
  //cout << " end EM ..." ;
  //cout << endl << endl << endl << endl;
  field <mat>	gmm_one_model(3); // #frames,(3=means +covs + weights)
  int rs = ceil(Ng/2);
  
  vec weights = em.get_weights();
  
  //weights.print("weights");
  mat weights2; // 08/08/2013
  if (weights.n_elem!=1)
  {
    weights2 = reshape(weights,rs,2);  // 08/08/2013 --> Due to change in Armadillo version
  }
  
  gmm_one_model(0) = em.get_means();
  gmm_one_model(1) = em.get_covs();
  gmm_one_model(2) = weights2; // Cambio realizado el 8 de Agosto de 2013. En la nueva version de Armadillo
  //cout << "... end GMM Model ... ";
  //   cout << "em.get_means()   " << endl << em.get_means()<< endl;
  //   cout << "press one key to continue ..." << endl;
  //   getchar();
  //   cout << "em.get_covs()    " << endl << em.get_covs() << endl;
  //   cout << "press one key to continue ..." << endl;
  //   getchar();
  //   cout << "em.get_weights() " << endl << em.get_weights()<< endl;
  //   cout << "press one key to continue ..." << endl;
  //   getchar();
  
  return gmm_one_model;
}

